package com.hz.common.utils;

import android.accounts.NetworkErrorException;

import com.hz.common.net.http.Request;
import com.hz.common.net.http.Response;
import com.hz.common.net.http.ResponseCallback;
import com.hz.common.net.tls.TlsClient;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Scanner;

public class HttpUtils {

    private boolean isHostnameVerifier;
    private boolean isX509TrustManager;
    private int READ_TIMEOUT_TIME = 19000;
    private int CONNECT_TIMEOUT_TIME = 15000;

    private HttpUtils() {
    }

    public static HttpUtils getInstance() {
        return HttpUtilsHolder.instance;
    }

    private static class HttpUtilsHolder {
        private static final HttpUtils instance = new HttpUtils();
    }

    /**
     * set isX509TrustManager
     *
     * @param isX509TrustManager true;false
     */
    public void isX509TrustManager(boolean isX509TrustManager) {
        this.isX509TrustManager = isX509TrustManager;
    }

    /**
     * set isHostnameVerifier
     *
     * @param isHostnameVerifier true;false
     */
    public void isHostnameVerifier(boolean isHostnameVerifier) {
        this.isHostnameVerifier = isHostnameVerifier;
    }

    /**
     * set timeout
     *
     * @param connectTimeout connect timeout
     * @param readTimeout    read timeout
     * @return current obj
     */
    public HttpUtils setTimeout(int connectTimeout, int readTimeout) {
        this.READ_TIMEOUT_TIME = readTimeout;
        this.CONNECT_TIMEOUT_TIME = connectTimeout;
        return this;
    }

    /**
     * request
     *
     * @param request  data
     * @param callback callback
     */
    public void call(final Request request, final ResponseCallback callback) {
        new Call(request, callback).run();
    }

    /**
     * request in thread
     *
     * @param request  data
     * @param callback callback
     */
    public void callThread(final Request request, final ResponseCallback callback) {
        new Thread(new Call(request, callback)).start();
    }

    /**
     * request
     *
     * @param url url
     * @return TlsClient
     * @throws Exception
     */
    public TlsClient callTlsGet(String url) throws Exception {
        TlsClient tlsClient = new TlsClient(url, TlsClient.HttpMethod.GET);
        String[] supports = {"SSLv3", "TLSv1", "TLSv1.2", "TLSv1.1"};
        tlsClient.setAllowedSSLProtocols(supports);
        tlsClient.setFollowRedirects(false);
        if (isHostnameVerifier) {
            tlsClient.setMyHostnameVerifier();
        }
        if (isX509TrustManager) {
            tlsClient.setMyX509TrustManager();
        }
        tlsClient.addHeader("Accept", "application/fido.trusted-apps+json");
        tlsClient.sendRequest();
        return tlsClient;
    }

    /**
     * POST + JSON
     *
     * @param data send data
     * @param url  target url
     * @return data receive from server
     * @author MilkZS
     */
    public String postJson(String data, String url) {
        return doHttpAction(data, true, true, url);
    }

    /**
     * POST + FORM
     *
     * @param data send data
     * @param url  target url
     * @return data receive from serv
     * @author MilkZS
     */
    public String postForm(String data, String url) {
        return doHttpAction(data, false, true, url);
    }

    /**
     * GET + JSON
     *
     * @param data send data
     * @param url  target url
     * @return data receive from server
     * @author MilkZS
     */
    public String getJson(String data, String url) {
        return doHttpAction(data, true, false, url);
    }

    /**
     * GET + FORM
     *
     * @param data send data
     * @param url  target url
     * @return data receive from server
     * @author MilkZS
     */
    public String getForm(String data, String url) {
        return doHttpAction(data, false, false, url);
    }

    private String doHttpAction(String data, boolean json, boolean post, String url) {
        HttpURLConnection connection = null;
        DataOutputStream os = null;
        InputStream is = null;
        try {
            URL sUrl = new URL(url);
            connection = (HttpURLConnection) sUrl.openConnection();
            connection.setConnectTimeout(CONNECT_TIMEOUT_TIME);
            connection.setReadTimeout(READ_TIMEOUT_TIME);
            if (post) {
                connection.setRequestMethod("POST");
            } else {
                connection.setRequestMethod("GET");
            }
            //允许输入输出
            connection.setDoInput(true);
            connection.setDoOutput(true);
            // 是否使用缓冲
            connection.setUseCaches(false);
            // 本次连接是否处理重定向，设置成true，系统自动处理重定向；
            // 设置成false，则需要自己从http reply中分析新的url自己重新连接。
            connection.setInstanceFollowRedirects(true);
            // 设置请求头里的属性
            if (json) {
                connection.setRequestProperty("Content-Type", "application/json");
            } else {
                connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                connection.setRequestProperty("Content-Length", data.length() + "");
            }
            connection.connect();

            os = new DataOutputStream(connection.getOutputStream());
            os.write(data.getBytes(), 0, data.getBytes().length);
            os.flush();
            os.close();

            is = connection.getInputStream();
            Scanner scan = new Scanner(is);
            scan.useDelimiter("\\A");
            if (scan.hasNext()) return scan.next();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) connection.disconnect();
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    private HttpURLConnection getConnection(final Request request) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) request.mURL.openConnection();
        addHeader(conn, request.mHeader);
        addBody(conn, request.mBody);
        conn.setReadTimeout(this.READ_TIMEOUT_TIME);
        conn.setConnectTimeout(this.CONNECT_TIMEOUT_TIME);
        return conn;
    }

    private void addBody(HttpURLConnection conn, Request.Body body) throws IOException {
        if (body == null) {
            conn.setRequestMethod("GET");
        } else {
            conn.setRequestMethod("POST");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            conn.setRequestProperty("content-type", body.mediaType);
            if (body.length > 0) {
                conn.setRequestProperty("content-length", String.valueOf(body.length));
            }
            BufferedOutputStream bos = new BufferedOutputStream(conn.getOutputStream(), 10240);
            if (body.bis != null) {
                byte[] buffer = new byte[10240];
                for (int len; (len = body.bis.read(buffer)) != -1; ) {
                    bos.write(buffer, 0, len);
                }
                bos.close();
                body.bis.close();
            }
        }
    }

    private void addHeader(final HttpURLConnection conn, final Map<String, String> headerMap) {
        if (headerMap != null) {
            for (String key : headerMap.keySet()) {
                conn.setRequestProperty(key, headerMap.get(key));
            }
        }
    }

    private boolean isSpace(final String s) {
        if (s == null) return true;
        for (int i = 0, len = s.length(); i < len; ++i) {
            if (!Character.isWhitespace(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    class Call implements Runnable {

        private final Request request;
        private final ResponseCallback callback;

        public Call(Request request, ResponseCallback callback) {
            this.request = request;
            this.callback = callback;
        }

        @Override
        public void run() {
            HttpURLConnection conn = null;
            try {
                conn = getConnection(request);
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    InputStream is = conn.getInputStream();
                    callback.onResponse(new Response(conn.getHeaderFields(), is));
                    is.close();
                } else if (responseCode == 301 || responseCode == 302) {
                    call(request, callback);
                } else {
                    String errorMsg = null;
                    InputStream es = conn.getErrorStream();
                    if (es != null) {
                        errorMsg = StringUtils.is2String(es, "utf-8");
                    }
                    String s = isSpace(errorMsg) ? "" : ("\n" + "error message: " + errorMsg);
                    callback.onFailed(new NetworkErrorException("error code: " + responseCode + s));
                }
            } catch (IOException e) {
                callback.onFailed(e);
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
            }
        }
    }
}
